// -----------------------------------------------------------------------------
// This source file is part of Matrix Platform
// 	(Universal .NET Software Development Platform)
// For the latest info, see http://www.matrixplatform.com
// 
// Copyright (c) 2009-2010, Ingenious Ltd
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;

#if Matrix_Diagnostics
using Matrix.Common.Diagnostics;
#endif

namespace Matrix.Framework.DataStorage.DataSeries
{
    /// <summary>
    /// Handles writing of series items to the RawDataWriter (and thus to a file).
    /// </summary>
    public class DataSeriesWriter : DataSeriesManipulator
    {
        object _syncRoot = new object();

        IItemSerializer _serializer;

        BinaryWriter _writer;

        uint _itemIndex = 0;
        /// <summary>
        /// Currently pending item index.
        /// </summary>
        public uint ItemIndex
        {
            get { return _itemIndex; }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public DataSeriesWriter()
        {
        }

        /// <summary>
        /// Release any resources.
        /// </summary>
        public override void Dispose()
        {
            _serializer = null;
            base.Dispose();
        }

        /// <summary>
        /// Initialize the writer for operation.
        /// </summary>
        /// <param name="serializer">The serializer that is used to convert items to data.</param>
        /// <param name="filePath">The full file path and name to the file to write to.</param>
        /// <param name="continueExisting">Continue writing to an existing file, if there is one.</param>
        public bool Initalize(IItemSerializer serializer, string filePath, bool continueExisting)
        {
            lock (_syncRoot)
            {
                if (_serializer != null)
                {
                    return false;
                }

                _serializer = serializer;
            }

            base.Initialize();

            FileMode fileMode = FileMode.Create;
            if (continueExisting && File.Exists(filePath))
            {
                fileMode = FileMode.OpenOrCreate;
            }

            try
            {
                lock (_syncRoot)
                {
                    // Init with 16K buffer (higher buffer does not seem to improve speed).
                    _fileStream = new FileStream(filePath, fileMode, 
                                                 FileAccess.ReadWrite, FileShare.Read, 16 * 1024);

                    // Seek to end.
                    _fileStream.Seek(0, SeekOrigin.End);
                }

                _writer = new BinaryWriter(_fileStream);

                if (continueExisting)
                {
                    uint? index = GetStreamLastIndex(serializer, filePath);

                    if (FileStreamSize > ItemHeaderSize + 1)
                    {
                        if (index.HasValue == false)
                        {
                            throw new OperationCanceledException("Failed to initialize.");
                        }
                        else
                        {
                            _itemIndex = index.Value + 1;
                        }
                    }
                    else
                    {// Empty file.
                        _itemIndex = 0;
                    }
                }
            }
            catch (Exception ex)
            {
#if Matrix_Diagnostics
                SystemMonitor.OperationError("Failed to initialize", ex);
#endif

                _serializer = null;
                return false;
            }

            return true;
        }

        /// <summary>
        /// Write a single item; use the Write(IEnumerable<object> items) version
        /// when writing multiple items for optimized performance.
        /// </summary>
        /// <param name="item">The item to be written.</param>
        /// <returns>True if write was success, otherwise false.</returns>
        public bool Write(object item)
        {
            return Write<object>(new object[] { item }) == 1;
        }

        /// <summary>
        /// Write multiple items.
        /// </summary>
        /// <param name="items">The items to be written.</param>
        /// <returns>The number of items written.</returns>
        public int Write<ItemType>(IEnumerable<ItemType> items)
            where ItemType : class
        {
            IItemSerializer serializer = _serializer;
            if (serializer == null)
            {
                return 0;
            }

            DataSeriesSequenceHelper sequenceHelper = SequenceHelper;
            if (sequenceHelper == null)
            {
                return 0;
            }

            FileStream fileStream = _fileStream;
            if (fileStream == null)
            {
                return 0;
            }

            int itemsWritten = 0;

            MemoryStream stream = new MemoryStream();
            using (BinaryWriter writer = new BinaryWriter(stream))
            {
                lock (_syncRoot)
                {// Make sure only one thread writes at a given moment.

                    // IMPORTANT, changing the structure here may mess with the 
                    // operation of the DataSeriesSequenceHelper, so make sure to 
                    // adjust accordingly (see the EscapeSequence notes).
                    
                    foreach (object item in items)
                    {
                        if (CreateItemToWriter(serializer, writer, sequenceHelper, stream, _itemIndex, item) == false)
                        {
                            return itemsWritten;
                        }

                        // Put the escape in.
                        fileStream.Write(sequenceHelper.EscapeSequence, 0, sequenceHelper.EscapeSequence.Length);

                        // Dump the item data.
                        fileStream.Write(stream.GetBuffer(), 0, (int)stream.Length);

                        itemsWritten++;
                        _itemIndex++;
                    }
                }
            }


            return itemsWritten;
        }

        ///// <summary>
        ///// Write item to writer.
        ///// </summary>
        //public static bool CreateItemToWriterTest(IItemSerializer serializer, BinaryWriter writer,
        //    DataSeriesSequenceHelper sequenceHelper, Stream stream, uint itemIndex, object item)
        //{
        //    // Reset.
        //    //writer.Seek(0, SeekOrigin.Begin);
        //    //stream.SetLength(0);

        //    // Write the index first.
        //    writer.Write(itemIndex);

        //    // Default indication of no escape sequences in user data.
        //    writer.Write(false);

        //    // Placeholder for the real length value.
        //    writer.Write(uint.MaxValue);

        //    long itemStart = stream.Length;
        //    if (serializer.WriteItem(writer, itemIndex, item) == false)
        //    {// Failed to write item.
        //        return false;
        //    }

        //    long itemDataSize = stream.Length - itemStart;

        //    // Write the actual size.
        //    //writer.Seek(sizeof(uint) + 1, SeekOrigin.Begin);
        //    //writer.Write((int)stream.Length);

        //    return true;
        //}
    }
}
